/*
 * Copyright 2020-2030 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitee.minimalismstyle.fresh.common.api.config

import feign.RequestInterceptor
import feign.RequestTemplate
import org.springframework.security.oauth2.client.OAuth2ClientContext
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException
import org.springframework.security.oauth2.client.token.AccessTokenProvider
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain
import org.springframework.security.oauth2.client.token.AccessTokenRequest
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider
import org.springframework.security.oauth2.common.OAuth2AccessToken

/**
 *  Feign OAuth2请求拦截器，将token增加到请求头中
 * @author maoxiaodong
 */
class FreshOAuth2FeignRequestInterceptor(private val oAuth2ClientContext: OAuth2ClientContext,
                                         private val resource: OAuth2ProtectedResourceDetails) : RequestInterceptor {
    private val tokenType: String = BEARER
    private val header: String = AUTHORIZATION
    private var accessTokenProvider: AccessTokenProvider = AccessTokenProviderChain(listOf(AuthorizationCodeAccessTokenProvider(), ImplicitAccessTokenProvider(), ResourceOwnerPasswordAccessTokenProvider(), ClientCredentialsAccessTokenProvider()))



    override fun apply(template: RequestTemplate) {
        val accessToken: OAuth2AccessToken? = this.oAuth2ClientContext.getAccessToken()
        if (accessToken != null) {
            template.header(header, *arrayOf(extract(tokenType)))
        }
    }

    private fun extract(tokenType: String): String {
        val accessToken: OAuth2AccessToken = token
        return java.lang.String.format("%s %s", tokenType, accessToken.value)
    }

    private val token: OAuth2AccessToken
        get() {
            var accessToken: OAuth2AccessToken? = oAuth2ClientContext.accessToken
            if (accessToken == null || accessToken.isExpired) {
                try {
                    accessToken = acquireAccessToken()
                } catch (var5: UserRedirectRequiredException) {
                    oAuth2ClientContext.accessToken = null
                    val stateKey: String? = var5.stateKey
                    if (stateKey != null) {
                        var stateToPreserve: Any? = var5.stateToPreserve
                        if (stateToPreserve == null) {
                            stateToPreserve = "NONE"
                        }
                        oAuth2ClientContext.setPreservedState(stateKey, stateToPreserve)
                    }
                    throw var5
                }
            }
            return accessToken
        }

    private fun acquireAccessToken(): OAuth2AccessToken {
        val tokenRequest: AccessTokenRequest? = oAuth2ClientContext.accessTokenRequest
        return if (tokenRequest == null) {
            throw AccessTokenRequiredException("Cannot find valid context on request for resource '" + resource.id.toString() + "'.", resource)
        } else {
            val stateKey: String? = tokenRequest.stateKey
            if (stateKey != null) {
                tokenRequest.preservedState = oAuth2ClientContext.removePreservedState(stateKey)
            }
            val existingToken: OAuth2AccessToken? = oAuth2ClientContext.accessToken
            if (existingToken != null) {
                oAuth2ClientContext.accessToken = existingToken
            }
            val obtainableAccessToken: OAuth2AccessToken = accessTokenProvider.obtainAccessToken(resource, tokenRequest)
            if (obtainableAccessToken.value != null) {
                oAuth2ClientContext.accessToken = obtainableAccessToken
                obtainableAccessToken
            } else {
                throw IllegalStateException(" Access token provider returned a null token, which is illegal according to the contract.")
            }
        }
    }

    fun setAccessTokenProvider(accessTokenProvider: AccessTokenProvider) {
        this.accessTokenProvider = accessTokenProvider
    }

    companion object {
        const val BEARER = "Bearer"
        const val AUTHORIZATION = "Authorization"
    }
}